package com.amway.qy.core;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amway.qy.aes.WXBizMsgCrypt;
import com.amway.qy.core.callback.QyWechatCallBackHandler;
import com.amway.qy.core.config.QyWechatConfig;
import com.amway.qy.core.constants.ReqMsgType;
import com.amway.qy.core.service.QyAccessTokenService;
import com.amway.qy.core.service.QyAsyncTaskService;
import com.amway.qy.core.service.QyConcreteMessagePushService;
import com.amway.qy.core.service.QyDepartmentSerivce;
import com.amway.qy.core.service.QyMemberService;
import com.amway.qy.core.service.QySecondAuthenticationService;
import com.amway.qy.core.service.QyTagService;
import com.amway.qy.core.service.QyWechatAuthService;
import com.amway.qy.core.service.QyWechatPayService;
import com.amway.qy.core.service.QyWechatServerInfoService;
import com.amway.qy.core.service.impl.DefaultAccessTokenImpl;
import com.amway.qy.core.service.impl.QyAsyncTaskServiceImpl;
import com.amway.qy.core.service.impl.QyDefaultMessagePushServiceImpl;
import com.amway.qy.core.service.impl.QyDepartmentServiceImpl;
import com.amway.qy.core.service.impl.QyMemberServiceImpl;
import com.amway.qy.core.service.impl.QySecondAuthenticationServiceImpl;
import com.amway.qy.core.service.impl.QyTagServiceImpl;
import com.amway.qy.core.service.impl.QyWechatAuthServiceImpl;
import com.amway.qy.core.service.impl.QyWechatPayServiceImpl;
import com.amway.qy.core.service.impl.QyWechatServerInfoServiceImpl;
import com.amway.qy.message.req.ReqAgentEntryEventMessage;
import com.amway.qy.message.req.ReqBaseMessage;
import com.amway.qy.message.req.ReqImageMessage;
import com.amway.qy.message.req.ReqLinkMessage;
import com.amway.qy.message.req.ReqLocationMessage;
import com.amway.qy.message.req.ReqMenuClickEventMessage;
import com.amway.qy.message.req.ReqMenuViewEventMessage;
import com.amway.qy.message.req.ReqShortVideo;
import com.amway.qy.message.req.ReqSubscribeEventMessage;
import com.amway.qy.message.req.ReqTextMessage;
import com.amway.qy.message.req.ReqUnSubscribeEventMessage;
import com.amway.qy.message.req.ReqVideoMessage;
import com.amway.qy.message.req.ReqVoiceMessage;

/*
 * 提供一个对微信企业号的统一入口。
 */
public final class QyWechatBox {
	
	private static final Logger logger = LoggerFactory.getLogger(QyWechatBox.class);
	
	/*
	 * 用于自动维护  ReqMessage 与 处理接口方法 的关联。
	 */
	private static final Map<Class<? extends ReqBaseMessage>,String> MethodMappingHelper = MethodInvokeHelper.valueOf(QyWechatCallBackHandler.class);
	
	private static QyWechatBox instance;
	
	private static WXBizMsgCrypt msgCrypt ;
	
	private QyAccessTokenService accessTokenManager;
	private QyDepartmentSerivce departmentManager;
	private QyMemberService memberManager;
	private QySecondAuthenticationService authenticationManager;
	private QyWechatServerInfoService wechatServerInfoManager;
	private QyConcreteMessagePushService messagePushManager;
	private QyTagService tagManager;
	private QyAsyncTaskService asyncTaskManager;
	private QyWechatPayService wechatPayManager;
	private QyWechatAuthService wechatAuthManager;
	
	private QyWechatConfig config;
	
	
	private QyWechatBox(QyWechatConfig config){
		this.config = config;
	};
	
	// 主要用于被其他的上层应用调用，以查看当前的底层包的相应配置
	public QyWechatConfig getConfig() {
		return config;
	}
	
	public synchronized static QyWechatBox initialize(final QyWechatConfig config) {
		logger.info("QyWechatBox initializing........................");
		if(config!=null 
				&&checkEmpty(config.getToken(),config.getCorpid(),config.getCorpsecret(),config.getsEncodingAESKey())){
			
			logger.error("Failed to QyWechatBox initializing!!!");
			throw new NullPointerException("Empty or Null String is invalid.");
		}
		
		if(instance!=null){
			return instance;
		}
		
		/*
		 * 完成相应 管理器 注入
		 */
		instance = new QyWechatBox(config);
		
		try {
			logger.info("WXBizMsgCrypt injecting...");
			msgCrypt = new WXBizMsgCrypt(config.getToken(), config.getsEncodingAESKey(), config.getCorpid());
		} catch (Exception e) {
			logger.error("Failed to create the WXBizMsgCrypt!", e);
			throw new RuntimeException(e);
		}
		
		logger.info("AccessTokenManager injecting...");
		//默认使用本地内存，来缓存 access_token,会提前15分钟超时。开发人员可以根据需要，重新注入新的管理器。
		instance.setAccessTokenManager(new DefaultAccessTokenImpl(config.getCorpid(),config.getCorpsecret()));
		
		logger.info("DepartmentManager injecting...");
		instance.setDepartmentManager(new QyDepartmentServiceImpl(instance.getAccessTokenManager()));
		
		logger.info("MemberManager injecting...");
		instance.setMemberManager(new QyMemberServiceImpl(instance.getAccessTokenManager()));
		
		logger.info("QySecondAnthenticationManager injecting...");
		instance.setAuthenticationManager(new QySecondAuthenticationServiceImpl(instance.getAccessTokenManager()));
		
		logger.info("QyWechatServerInfoManager injecting...");
		instance.setWechatServerInfoManager(new QyWechatServerInfoServiceImpl(instance.getAccessTokenManager()));
		
		logger.info("QyMessagePushManager injecting...");
		instance.setMessagePushManager(new QyDefaultMessagePushServiceImpl(instance.getAccessTokenManager()));
		
		logger.info("QyTagManager injecting...");
		instance.setTagManager(new QyTagServiceImpl(instance.getAccessTokenManager()));
		
		logger.info("QyAsyncTaskManager injecting...");
		instance.setAsyncTaskManager(new QyAsyncTaskServiceImpl(instance.getAccessTokenManager()));
		
		logger.info("QyWechatPayManager injecting...");
		instance.setWechatPayManager(new QyWechatPayServiceImpl(instance.getAccessTokenManager()));
		
		logger.info("QyWechatAuthManager injecting...");
		instance.setWechatAuthManager(new QyWechatAuthServiceImpl(instance.getAccessTokenManager()));
		
		logger.info("QyWechatBox initialized Successfully...");
		
		return instance;		 
	}
	
	/*
	 * 该方法将作为处理 微信 回调方法的核心入口
	 */
	public String processRequest(final Map<String,String> postData,QyWechatCallBackHandler callback) throws Exception{

		ReqBaseMessage message = parseReqeuseToMessage(postData);
			
		String ret = invokeCallbackMethodByRequest(postData,message,callback);
		
		if(ret == null){
			ret = "";
		}
		// 微信端接收的时间标记是以秒为单位，而java默认是毫秒，故必除以1000
		return msgCrypt.EncryptMsg(ret,String.valueOf(System.currentTimeMillis()/1000),"QY_WeChat_Hongyu");
		
	}
	
	/*
	 * 该方法主要是通过 反射机制，自动根据请求的消息类型，寻找对应的处理方法执行。
	 * 这样的好处是，当#{QyWechatCallBackHandler}的接口方法发生变化时，#link{QyWechatBox}会自动修正。
	 */
	private String invokeCallbackMethodByRequest(final Map<String,String> postData,final ReqBaseMessage requestMessage, final QyWechatCallBackHandler handler) throws Exception{
		
		String result = null;
		
		String methodName = MethodMappingHelper.get(requestMessage.getClass());
		
		if(null == methodName){
			result = handler.handle(postData);
		}else{
			Method method = handler.getClass().getMethod(methodName, requestMessage.getClass());
			method.setAccessible(true);
			result = String.class.cast(method.invoke(handler, requestMessage));
		}
		return result;
	}
	
	private ReqBaseMessage parseReqeuseToMessage(Map<String,String> postData){
		
		String msgType = postData.get("MsgType");
		
		String toUserName = postData.get("ToUserName");
		String fromUserName = postData.get("FromUserName");
		Long createTime = Long.valueOf(postData.get("CreateTime"));
		Long msgId = null;
		if(postData.get("MsgId")!=null){
			msgId = Long.valueOf(postData.get("MsgId"));
		}
		Long agentID = Long.valueOf(postData.get("AgentID"));
		
		
		if(msgType == null){
			logger.error("parseReqeuseToMessage  --> " + msgType);
			throw new NullPointerException("Msg Type is Null!!!");
		}
		
		if(msgType.equals(ReqMsgType.REQ_MESSAGE_TYPE_TEXT)){
			
			String content = postData.get("Content");
			
			ReqTextMessage text = new ReqTextMessage();
			text.setAgentID(agentID);
			text.setToUserName(toUserName);
			text.setFromUserName(fromUserName);
			text.setCreateTime(createTime);
			text.setMsgId(msgId);
			text.setContent(content);
			
			return text;
		}else if(msgType.equals(ReqMsgType.REQ_MESSAGE_TYPE_IMAGE)){
			String picUrl = postData.get("PicUrl");
			String mediaId= postData.get("MediaId");
			
			ReqImageMessage img = new ReqImageMessage();
			img.setAgentID(agentID);
			img.setToUserName(toUserName);
			img.setFromUserName(fromUserName);
			img.setCreateTime(createTime);
			img.setMsgId(msgId);
			img.setPicUrl(picUrl);
			img.setMediaId(mediaId);
			
			return img;
				
		}else if(msgType.equals(ReqMsgType.REQ_MESSAGE_TYPE_VOICE)){
			String format = postData.get("Format");
			String mediaId= postData.get("MediaId");
			
			ReqVoiceMessage voice = new ReqVoiceMessage();
			voice.setAgentID(agentID);
			voice.setToUserName(toUserName);
			voice.setFromUserName(fromUserName);
			voice.setCreateTime(createTime);
			voice.setMsgId(msgId);
			voice.setMediaId(mediaId);
			voice.setFormat(format);
			
			return voice;
		}else if(msgType.equals(ReqMsgType.REQ_MESSAGE_TYPE_VIDEO)){
			String thumbMediaId = postData.get("ThumbMediaId");
			String mediaId= postData.get("MediaId");
			
			ReqVideoMessage video = new ReqVideoMessage();
			video.setAgentID(agentID);
			video.setToUserName(toUserName);
			video.setFromUserName(fromUserName);
			video.setCreateTime(createTime);
			video.setMsgId(msgId);
			video.setMediaId(mediaId);
			video.setThumbMediaId(thumbMediaId);
			
			return video;
		}else if(msgType.equals(ReqMsgType.REQ_MESSAGE_TYPE_SHORT_VIDEO)){
			String thumbMediaId = postData.get("ThumbMediaId");
			String mediaId= postData.get("MediaId");
			
			ReqShortVideo short_video = new ReqShortVideo();
			short_video.setAgentID(agentID);
			short_video.setToUserName(toUserName);
			short_video.setFromUserName(fromUserName);
			short_video.setCreateTime(createTime);
			short_video.setMsgId(msgId);
			short_video.setMediaId(mediaId);
			short_video.setThumbMediaId(thumbMediaId);
			
			return short_video;
		}else if(msgType.equals(ReqMsgType.REQ_MESSAGE_TYPE_LOCATION)){
			Float location_X = Float.valueOf(postData.get("Location_X"));
			Float location_Y = Float.valueOf(postData.get("Location_Y"));
			Integer scale = Integer.valueOf(postData.get("Scale"));
			String label = postData.get("Label");
			
			ReqLocationMessage location = new ReqLocationMessage();
			location.setAgentID(agentID);
			location.setToUserName(toUserName);
			location.setFromUserName(fromUserName);
			location.setCreateTime(createTime);
			location.setMsgId(msgId);
			location.setLabel(label);
			location.setLocation_X(location_X);
			location.setLocation_Y(location_Y);
			location.setScale(scale);
			
			return location;
			
		}else if(msgType.equals(ReqMsgType.REQ_MESSAGE_TYPE_LINK)){
			String title = postData.get("Title"); 
			String description = postData.get("Description"); 
			String picUrl = postData.get("PicUrl"); 
			
			ReqLinkMessage link = new ReqLinkMessage();
			link.setAgentID(agentID);
			link.setToUserName(toUserName);
			link.setFromUserName(fromUserName);
			link.setCreateTime(createTime);
			link.setMsgId(msgId);
			link.setTitle(title);
			link.setDescription(description);
			link.setPicUrl(picUrl);
			
			return link;
			
		}else if(msgType.equals(ReqMsgType.REQ_MESSAGE_TYPE_EVENT)){
			String event = postData.get("Event");
			if(event.equals(ReqMsgType.EVENT_TYPE_SUBSCRIBE)){
				
				ReqSubscribeEventMessage subscribe = new ReqSubscribeEventMessage();
				subscribe.setAgentID(agentID);
				subscribe.setToUserName(toUserName);
				subscribe.setFromUserName(fromUserName);
				subscribe.setCreateTime(createTime);
				subscribe.setMsgId(msgId);
				
				return subscribe;
			}else if(event.equals(ReqMsgType.EVENT_TYPE_UNSUBSCRIBE)){
				ReqUnSubscribeEventMessage unsubscribe = new ReqUnSubscribeEventMessage();
				unsubscribe.setAgentID(agentID);
				unsubscribe.setToUserName(toUserName);
				unsubscribe.setFromUserName(fromUserName);
				unsubscribe.setCreateTime(createTime);
				unsubscribe.setMsgId(msgId);
				
				return unsubscribe;
			}else if(event.equals(ReqMsgType.EVENT_TYPE_CLICK)){
				String eventKey = postData.get("EventKey");
				ReqMenuClickEventMessage click = new ReqMenuClickEventMessage();
				click.setAgentID(agentID);
				click.setToUserName(toUserName);
				click.setFromUserName(fromUserName);
				click.setCreateTime(createTime);
				click.setMsgId(msgId);
				click.setEventKey(eventKey);
				
				return click;
			}else if(event.equals(ReqMsgType.EVENT_TYPE_VIEW)){
				String eventKey = postData.get("EventKey");
				ReqMenuViewEventMessage view = new ReqMenuViewEventMessage();
				view.setAgentID(agentID);
				view.setToUserName(toUserName);
				view.setFromUserName(fromUserName);
				view.setCreateTime(createTime);
				view.setMsgId(msgId);
				view.setEventKey(eventKey);
				
				return view;
			}else if(event.equals(ReqMsgType.EVENT_TYPE_ENTER_AGENT)){
				String eventKey = postData.get("EventKey");
				ReqAgentEntryEventMessage enter_agt_event = new ReqAgentEntryEventMessage();
				enter_agt_event.setAgentID(agentID);
				enter_agt_event.setToUserName(toUserName);
				enter_agt_event.setFromUserName(fromUserName);
				enter_agt_event.setCreateTime(createTime);
				enter_agt_event.setMsgId(msgId);
				enter_agt_event.setEventKey(eventKey);
				
			}
		}
		
		return null;
	}
	
	
	public String verifyURL(String msgSignature, String timeStamp, String nonce, String echoStr) throws Exception{
		return msgCrypt.VerifyURL(msgSignature, timeStamp, nonce, echoStr);
	}
	
	public String decryptMsg(String msgSignature, String timeStamp, String nonce, String postData) throws Exception{
		return msgCrypt.DecryptMsg(msgSignature, timeStamp, nonce, postData);
	}	
	
	private static boolean checkEmpty(String ...source){
		boolean empty = false;
		for(String s : source){
			if(s == null || s.trim().isEmpty()){
				empty = true;
				break;
			}
		}
		return empty;
	}
	
	/*
	 * @Description
	 * 		由于 默认的AccessToken缓存管理是基于本地缓存实现，但用户需要实现自定义的 AccessToken Service，则必须在 QyWechatBox 实例化后，重新更新AccessToken管理器。
	 */
	public void modifyAccessTokenManager(QyAccessTokenService tokenService){
		this.setAccessTokenManager(tokenService);
		this.getAuthenticationManager().setQyAccessTokenService(tokenService);
		this.getDepartmentManager().setQyAccessTokenService(tokenService);
		this.getMemberManager().setQyAccessTokenService(tokenService);
		this.getWechatServerInfoManager().setQyAccessTokenService(tokenService);
		this.getMessagePushManager().setQyAccessTokenService(tokenService);
		this.getTagManager().setQyAccessTokenService(tokenService);
		this.getAsyncTaskManager().setQyAccessTokenService(tokenService);
		this.getWechatPayManager().setQyAccessTokenService(tokenService);
		this.getWechatAuthManager().setQyAccessTokenService(tokenService);
	}
	
	/*
	 * @description 
	 * 		考虑到接受的消息或者事件类型比较多，而且变动也大，故将 请求的消息或者事件动作与对应处理器的处理方法进行映射。
	 */
	private static class MethodInvokeHelper{
		public static Map<Class<? extends ReqBaseMessage>,String> valueOf(Class<? extends QyWechatCallBackHandler> handlerClz){
			final Map<Class<? extends ReqBaseMessage>,String> map = new HashMap<Class<? extends ReqBaseMessage>, String>();
			Method[]  ms = handlerClz.getMethods();
			for(Method m : ms){
				@SuppressWarnings("unchecked")
				Class<? extends ReqBaseMessage> type = (Class<? extends ReqBaseMessage>) m.getParameterTypes()[0];
				 map.put((Class<? extends ReqBaseMessage>) type, m.getName());
			}
			return map;
		}
	}
	
	//--------------------------- Getter and Setter -------------------------------
	public QyAccessTokenService getAccessTokenManager() {
		return accessTokenManager;
	}

	public void setAccessTokenManager(QyAccessTokenService accessTokenManager) {
		this.accessTokenManager = accessTokenManager;
	}

	public QyDepartmentSerivce getDepartmentManager() {
		return departmentManager;
	}

	public void setDepartmentManager(QyDepartmentSerivce departmentManager) {
		this.departmentManager = departmentManager;
	}

	public QyMemberService getMemberManager() {
		return memberManager;
	}

	public void setMemberManager(QyMemberService memberManager) {
		this.memberManager = memberManager;
	}

	public QySecondAuthenticationService getAuthenticationManager() {
		return authenticationManager;
	}

	public void setAuthenticationManager(QySecondAuthenticationService authenticationManager) {
		this.authenticationManager = authenticationManager;
	}

	public QyWechatServerInfoService getWechatServerInfoManager() {
		return wechatServerInfoManager;
	}

	public void setWechatServerInfoManager(QyWechatServerInfoService wechatServerInfoManager) {
		this.wechatServerInfoManager = wechatServerInfoManager;
	}

	public QyConcreteMessagePushService getMessagePushManager() {
		return messagePushManager;
	}

	public void setMessagePushManager(QyConcreteMessagePushService messagePushManager) {
		this.messagePushManager = messagePushManager;
	}

	public QyTagService getTagManager() {
		return tagManager;
	}

	public void setTagManager(QyTagService tagManager) {
		this.tagManager = tagManager;
	}

	public QyAsyncTaskService getAsyncTaskManager() {
		return asyncTaskManager;
	}

	public void setAsyncTaskManager(QyAsyncTaskService asyncTaskManager) {
		this.asyncTaskManager = asyncTaskManager;
	}

	public QyWechatPayService getWechatPayManager() {
		return wechatPayManager;
	}

	public void setWechatPayManager(QyWechatPayService wechatPayManager) {
		this.wechatPayManager = wechatPayManager;
	}

	public QyWechatAuthService getWechatAuthManager() {
		return wechatAuthManager;
	}

	public void setWechatAuthManager(QyWechatAuthService wechatAuthManager) {
		this.wechatAuthManager = wechatAuthManager;
	}
	
}
